#ifndef PROGRAM_SPECIFY_HPP
#define PROGRAM_SPECIFY_HPP

#include "../Platform.hpp"
#include <iostream>
#include <vector>

namespace Program {

static const GLchar *glsl_version = "#version 450";

enum ShaderStage {
  VERTEX,
  GEOMETRY,
  FRAGMENT,
  TESSLETION_CONTROL,
  TESSLETION_EVALUATE,
  COMPUTE,
  VERTEX_FRAGMENT,
  VERTEX_GEO_FRAG,
  VERTEX_GEO,
  FRAG_GEO,
  VERTEX_GEO_FRAG_TESSC,
  ALL,
};

enum ProgramParameter {
  SEPERATE_PROGRAM,
  PROGRAM_BINARY_RETRIEVABLE_HINT,
};

using ShaderType = ShaderStage;

static GLenum GetGLShaderType(ShaderType type) {
  static GLenum table[] = {GL_VERTEX_SHADER,          GL_GEOMETRY_SHADER,
                           GL_FRAGMENT_SHADER,        GL_TESS_CONTROL_SHADER,
                           GL_TESS_EVALUATION_SHADER, GL_COMPUTE_SHADER};
  return table[static_cast<size_t>(type)];
}

static GLenum GetGLValProPara(ProgramParameter param) {
  static GLenum table[] = {GL_PROGRAM_SEPARABLE, GL_PROGRAM_BINARY_RETRIEVABLE_HINT};
  return table[static_cast<size_t>(param)];
}

static void SetProgramParameter(GLuint id, ProgramParameter param, bool value) {
  glProgramParameteri(id, GetGLValProPara(param), GLint(value));
}

static bool GetProgramParameter(GLuint id, ProgramParameter param) {
  GLint p;
  glGetProgramiv(id, GetGLValProPara(param), &p);
  return p;
}

static bool CheckProgramLinkingError(GLuint id) {
  if (!glIsProgram(id))
    return false;
  GLint param;
  glGetProgramiv(id, GL_LINK_STATUS, &param);
  if (!param) {
    GLchar info[255];
    glGetProgramiv(id, GL_INFO_LOG_LENGTH, &param);
    glGetProgramInfoLog(id, 255, nullptr, info);
    std::cerr << "Link Error: \n -id " << id << "\n- info " << info << std::endl;
    return false;
  }
  return true;
}

static bool CheckShaderCompileError(GLuint id) {
  if (!glIsShader(id))
    return false;
  GLint param;
  glGetShaderiv(id, GL_COMPILE_STATUS, &param);
  // GLchar source[1024] = {'\0'};
  // glGetShaderSource(id, 1024, nullptr, source);
  // std::cout << source << std::endl;
  if (!param) {
    GLchar info[255];
    glGetShaderiv(id, GL_SHADER_TYPE, &param);
    std::string log;
    constexpr GLchar *text = " Shader Compile Error\n";
    switch (param) {
    case GL_VERTEX_SHADER:
      log.append("[Vertex]");
      break;
    case GL_FRAGMENT_SHADER:
      log.append("[Fragment]");
      break;
    case GL_GEOMETRY_SHADER:
      log.append("[Geometry]");
      break;
    case GL_TESS_CONTROL_SHADER:
      log.append("[TessControl]");
      break;
    case GL_TESS_EVALUATION_SHADER:
      log.append("[TessEvaluation]");
      break;
    case GL_COMPUTE_SHADER:
      log.append("[Compute");
      break;
    default:
      log.append("[Error-Type]");
      break;
    }
    log.append(text);
    glGetShaderInfoLog(id, 255, nullptr, info);
    std::cerr << log << "- id " << id << "\n- info \n" << info << std::endl;
    return false;
  }
  return true;
}

static void CheckActiveAttributes(GLuint id, const std::vector<GLuint> attributes) {
  std::cout << "Active Attributes: \n";
  for (const auto &val : attributes) {
    GLchar names[50] = {0};
    GLint bufsize;
    GLenum bufType;
    glGetActiveAttrib(id, val, 50, nullptr, &bufsize, &bufType, names);
    if (names[0] != '\0') {
      std::cout << "---------------\n";
      std::cout << "- Name: " << names << '\n';
      std::cout << "- Size: " << bufsize << '\n';
      std::cout << "- Type: " << (bufType == GL_FLOAT_VEC3 ? "FLOAT_VEC3" : "Unknown")
                << '\n';
      std::cout << "- Location: " << glGetAttribLocation(id, names) << std::endl;
    }
  }
}

static GLenum GetGLValFromShaderStage(ShaderStage type) {
  static GLenum table[] = {GL_VERTEX_SHADER_BIT,
                           GL_GEOMETRY_SHADER_BIT,
                           GL_FRAGMENT_SHADER_BIT,
                           GL_TESS_CONTROL_SHADER_BIT,
                           GL_TESS_EVALUATION_SHADER_BIT,
                           GL_COMPUTE_SHADER_BIT,
                           GL_VERTEX_SHADER_BIT | GL_FRAGMENT_SHADER_BIT,
                           GL_VERTEX_SHADER_BIT | GL_FRAGMENT_SHADER_BIT |
                               GL_GEOMETRY_SHADER_BIT,
                           GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT,
                           GL_FRAGMENT_SHADER_BIT | GL_GEOMETRY_SHADER_BIT,
                           GL_VERTEX_SHADER_BIT | GL_FRAGMENT_SHADER_BIT |
                               GL_GEOMETRY_SHADER_BIT | GL_TESS_CONTROL_SHADER_BIT,
                           GL_ALL_SHADER_BITS};
  return table[static_cast<size_t>(type)];
}
} // namespace Program

#endif